/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hannesdorfmann.swiperefresh.refresh;

import com.hannesdorfmann.swiperefresh.gesture.GestureDetector;
import com.hannesdorfmann.swiperefresh.util.ScrollUtil;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ScrollHelper;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

/**
 * Drop down refresh component
 *
 * @date 2020/12/27
 */
public class SwipeRefreshLayout extends ComponentContainer implements
        Component.TouchEventListener, Component.LayoutRefreshedListener, IRefresh {

    private GestureDetector mGestureDetector;
    private HeadOverComponent.RefreshState mState;
    private IRefresh.RefreshListener mRefreshListener;
    /**
     * Drop down refresh header component
     */
    protected HeadOverComponent mHeadOverComponent;
    private int mLastY;
    /**
     * Do you want to disable scrolling when the pull-down refresh
     */
    private boolean disableRefreshScroll;
    private AutoScroller mAutoScroll;

//    boolean mRefreshing;

    public SwipeRefreshLayout(Context context) {
        this(context, null);
    }

    public SwipeRefreshLayout(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    public SwipeRefreshLayout(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        // Call settoucheventlistener and let the ontouchevent method execute
        setTouchEventListener(this);
        // Call setlayoutrefreshedlistener and let the onrefresh method execute
        setLayoutRefreshedListener(this);
        mGestureDetector = new GestureDetector(gestureDetector);
        mAutoScroll = new AutoScroller(this);
    }

    private final GestureDetector.OnGestureListener gestureDetector = new GestureDetector.OnGestureListener() {
        @Override
        public boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY) {
            if (Math.abs(distanceX) > Math.abs(distanceY)) {
                // Horizontal scrolling occurs, events are not handled separately, so that the system can distribute events normally
                return false;
            }
            if (mRefreshListener != null && !mRefreshListener.enableRefresh()) {
                // Drop down refresh is not allowed, events are not handled separately, and the system can distribute events normally
                return false;
            }
            if (disableRefreshScroll && mState == HeadOverComponent.RefreshState.STATE_REFRESH) {
                // When refreshing, the list page is not allowed to scroll, return true, consume events, and the list page is not allowed to scroll
                return true;
            }
            Component head = getComponentAt(0);
            // Find components that can be scrolled
            Component child = ScrollUtil.findScrollableChild(SwipeRefreshLayout.this);
            // Determine whether the component is scrolling
            if (ScrollUtil.childScrolled(child)) {
                // The component scrolls and does not handle events separately, so that the system can distribute events normally
                return false;
            }
            // There is no refresh or the refresh distance is not reached, and the head has been drawn or pulled down
            if ((mState != HeadOverComponent.RefreshState.STATE_REFRESH || head.getBottom() <= mHeadOverComponent.mPullRefreshHeight) &&
                    (head.getBottom() != 0 || distanceY <= 0)) {
                // It's still sliding
                if (mState != HeadOverComponent.RefreshState.STATE_OVER_RELEASE) {
                    int offsetY;
                    // Calculate the required sliding distance according to the damping
                    if (head.getTop() < mHeadOverComponent.mPullRefreshHeight) {
                        // No distance to refresh, reduce damping
                        offsetY = (int) (mLastY / mHeadOverComponent.minDamp);
                    } else {
                        // Reach the refreshing distance and increase the damping
                        offsetY = (int) (mLastY / mHeadOverComponent.maxDamp);
                    }
                    // If the state is being refreshed, it is not allowed to change the state while sliding
                    // The second parameter of the movedown method is passed to false
                    boolean bool = moveDown(offsetY, false);
                    mLastY = (int) - distanceY;
                    return bool;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    };

    /**
     * Scrolls the head assembly and subcomponents based on the offset
     *
     * @param offsetY
     * @param auto Scrolls the head assembly and subcomponents based on the offset
     * @return
     */
    private boolean moveDown(int offsetY, boolean auto) {
        Component head = getComponentAt(0);
        Component child = getComponentAt(1);
        int childTop = child.getTop() + offsetY;
        if (childTop <= 0) {
            // Exception handling
            offsetY = -child.getTop();
            // Move to original position
            offsetTopAndBottom(head, offsetY);
            offsetTopAndBottom(child, offsetY);
            if (mState != HeadOverComponent.RefreshState.STATE_REFRESH) {
                mState = HeadOverComponent.RefreshState.STATE_INIT;
            }
        } else if (mState == HeadOverComponent.RefreshState.STATE_REFRESH && childTop > mHeadOverComponent.mPullRefreshHeight) {
            // Refreshing, do not continue to pull down
            return false;
        } else if (childTop <= mHeadOverComponent.mPullRefreshHeight) {
            // The refresh distance is not exceeded
            if (mHeadOverComponent.getState() != HeadOverComponent.RefreshState.STATE_REFRESH && !auto) {
                // The head starts to show
                mHeadOverComponent.onVisible();
                mHeadOverComponent.setState(HeadOverComponent.RefreshState.STATE_VISIBLE);
                mState = HeadOverComponent.RefreshState.STATE_VISIBLE;
            }
            offsetTopAndBottom(head, offsetY);
            offsetTopAndBottom(child, offsetY);
            if (childTop == mHeadOverComponent.mPullRefreshHeight && mState == HeadOverComponent.RefreshState.STATE_OVER_RELEASE) {
                // Start refreshing
                refresh();
            }
        } else {
            if (mHeadOverComponent.getState() != HeadOverComponent.RefreshState.STATE_OVER && !auto) {
                // 超出刷新位置
                mHeadOverComponent.onOver();
                mHeadOverComponent.setState(HeadOverComponent.RefreshState.STATE_OVER);
            }
            offsetTopAndBottom(head, offsetY);
            offsetTopAndBottom(child, offsetY);
        }
        if (mHeadOverComponent != null) {
            mHeadOverComponent.onScroll(head.getBottom(), mHeadOverComponent.mPullRefreshHeight);
        }
        return true;
    }

    /**
     * Modify the position of the component to achieve the effect of scrolling, only modify the vertical position of the component
     *
     * @param component
     * @param offset
     */
    public void offsetTopAndBottom(Component component, int offset) {
        component.setTop(component.getTop() + offset);
        component.setBottom(component.getBottom() + offset);
        component.setComponentPosition(component.getLeft(), component.getTop(), component.getRight(), component.getBottom());
    }

    /**
     * Modify the position of the component to achieve the effect of scrolling, only modify the horizontal position of the component
     *
     * @param component
     * @param offset
     */
    public void offsetLeftAndRight(Component component, int offset) {
        component.setLeft(component.getLeft() + offset);
        component.setRight(component.getRight() + offset);
        component.setComponentPosition(component.getLeft(), component.getTop(), component.getRight(), component.getBottom());
    }

    /**
     * Start refreshing
     */
    public void refresh() {
        if (mRefreshListener != null) {
            mHeadOverComponent.onRefresh();
            mRefreshListener.onRefresh();
            mState = HeadOverComponent.RefreshState.STATE_REFRESH;
            mHeadOverComponent.setState(HeadOverComponent.RefreshState.STATE_REFRESH);
        }
    }

    /**
     * Call back when the component is rearranged. In this method, you can call setcomponentposition method to determine the location of the component.
     * To execute this method, you need to call the setlayoutrefreshedlistener method. Here, the setlayoutrefreshedlistener method is called in the construction method
     *
     * @param component The current component object is swiperefreshlayout
     */
    @Override
    public void onRefreshed(Component component) {
        int left = component.getLeft();
        int top = component.getTop();
        int right = component.getRight();
        int bottom = component.getBottom();
        Component head = getComponentAt(0);
        Component child = getComponentAt(1);
        if (head != null && child != null) {
            int childTop = child.getTop();
            if (mState == HeadOverComponent.RefreshState.STATE_REFRESH) {
                head.setComponentPosition(0, mHeadOverComponent.mPullRefreshHeight - head.getHeight(), right, mHeadOverComponent.mPullRefreshHeight);
                child.setComponentPosition(0, mHeadOverComponent.mPullRefreshHeight, right, mHeadOverComponent.mPullRefreshHeight + child.getHeight());
            } else {
                head.setComponentPosition(0, childTop - head.getHeight(), right, childTop);
                child.setComponentPosition(0, childTop, right, childTop + child.getHeight());
            }

            Component other;
            // The remaining components can move without following the gesture to achieve some special effects, such as levitation
            for (int i = 2; i < getChildCount(); ++i) {
                other = getComponentAt(i);
                other.setComponentPosition(0, top, right, bottom);
            }
        }
    }

    /**
     * To handle the touch events assigned to components, the settoucheventlistener method needs to be called in order to execute the method. Here, the settoucheventlistener method is called in the construction method
     *
     * @param component
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent ev) {
        Component head = getComponentAt(0);
        if (ev.getAction() == TouchEvent.CANCEL ||
                ev.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            // Let go of your hand
            if (head.getBottom() > 0) {
                // The head view is pulled down
                if (mState != HeadOverComponent.RefreshState.STATE_REFRESH) {
                    // It's not refreshing when you release your hand
                    recover(head.getBottom());
                    return false;
                }
            }
            mLastY = 0;
        }
        // After handling the lifting event, hand gesture to gesture processor
        boolean consumed = mGestureDetector.onTouchEvent(ev);
        if (head.getBottom() != 0 && consumed) {
            // The head view is pulled down and events are consumed
            return true;
        }
        if (head.getBottom() != 0
                && mState != HeadOverComponent.RefreshState.STATE_INIT
                && mState != HeadOverComponent.RefreshState.STATE_REFRESH) {
            // The head view is pulled down, and the current state is not the initial state, and is not the refresh state
            return true;
        }
        if (consumed) {
            // Consumption events, events no longer pass down
            return true;
        } else {
            return true;//false;
        }
    }

    private void recover(int dis) {
        if (mRefreshListener != null && dis > mHeadOverComponent.mPullRefreshHeight) {
            /*
             * Because the pull-down distance exceeds the minimum distance of the pull-down refresh, you need to scroll up a certain distance first,
             * That is, you need to scroll to the mHiOverView.mPullRefreshHeight This distance, and then it starts to refresh,
             * How far does it take to roll up to get there mHiOverView.mPullRefreshHeight What's the distance?
             * dis - mHiOverView.mPullRefreshHeight It's the distance you need to roll up
             */
            mAutoScroll.recover(dis - mHeadOverComponent.mPullRefreshHeight);
            mState = HeadOverComponent.RefreshState.STATE_OVER_RELEASE;
        } else {
            // The minimum distance of drop-down refresh is not reached, that is, it is not reached mHiOverView.mPullRefreshHeight This distance,
            // You should scroll up directly, that is, hide the head view
            mAutoScroll.recover(dis);
        }
    }

    /**
     * Do you want to disable scrolling when the pull-down refresh
     *
     * @param disableRefreshScroll True forbids scrolling, false allows scrolling
     */
    @Override
    public void setDisableRefreshScroll(boolean disableRefreshScroll) {
        this.disableRefreshScroll = disableRefreshScroll;
    }

    /**
     * Refresh complete
     */
    @Override
    public void refreshFinish() {
        TaskDispatcher dispatcher =  getContext().getMainTaskDispatcher();
        dispatcher.delayDispatch(new Runnable() {
            @Override
            public void run() {
                finishRefreshView();
            }
        }, 50);


    }


    private void finishRefreshView(){
        //Return in non refresh
        if( mState != HeadOverComponent.RefreshState.STATE_REFRESH){
            return;
        }
        Component head = getComponentAt(0);
        mHeadOverComponent.onFinish();
        mHeadOverComponent.setState(HeadOverComponent.RefreshState.STATE_INIT);
        int bottom = head.getBottom();
        if (bottom > 0) {
            // Refresh complete hide head view
            recover(bottom);
        }
        mState = HeadOverComponent.RefreshState.STATE_INIT;
    }

    /**
     * Set drop down refresh monitor
     *
     * @param listener
     */
    @Override
    public void setRefreshListener(RefreshListener listener) {
        mRefreshListener = listener;
    }

    /**
     * Set the head component of the drop-down refresh
     *
     * @param headOverComponent
     */
    @Override
    public void setRefreshOverComponent(HeadOverComponent headOverComponent) {
        if (mHeadOverComponent != null) {
            removeComponent(mHeadOverComponent);
        }
        mHeadOverComponent = headOverComponent;
        LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT);
        addComponent(mHeadOverComponent, 0, config);
    }

    private static class AutoScroller implements Runnable {

        private final ScrollHelper mScroller;
        private int mLastY;
        private boolean mIsFinish;

        private EventHandler mEventHandler;
        private SwipeRefreshLayout mSwipeRefreshLayout;

        public AutoScroller(SwipeRefreshLayout swipeRefreshLayout) {
            mSwipeRefreshLayout = swipeRefreshLayout;
            mScroller = new ScrollHelper();
            mEventHandler = new EventHandler(EventRunner.getMainEventRunner());
            mIsFinish = true;
        }

        @Override
        public void run() {
            if (!mScroller.isFinished()) {
                mScroller.updateScroll();
                mSwipeRefreshLayout.moveDown(mLastY - mScroller.getCurrValue(ScrollHelper.AXIS_Y), true);
                mLastY = mScroller.getCurrValue(ScrollHelper.AXIS_Y);
                mEventHandler.postTask(this);
            } else {
                mEventHandler.removeTask(this);
                mIsFinish = true;
            }
        }

        void recover(int dis) {
            mEventHandler.removeTask(this);
            mLastY = 0;
            mIsFinish = false;
            mScroller.startScroll(0, 0, 0, dis);
            mEventHandler.postTask(this);
        }

        boolean isFinish() {
            return mIsFinish;
        }
    }


    public void setRefreshing(boolean refreshing) {

    }


}
